package net.wzw.service.impl;

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.RemovalCause;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.wzw.constant.RedisKeyConstant;
import net.wzw.mapper.db_counter.FoodCounterMapper;
import net.wzw.mapper.db_counter.UserCounterMapper;
import net.wzw.redis.RedisCache;
import net.wzw.service.BlurCounterService;
import net.wzw.utils.ApiResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;

/**
 * @author wzw
 * @date 2025/5/1
 * @Description
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class BlurCounterServiceImpl implements BlurCounterService {

    //冷数据添加到缓存过期时间
    private final Long DATABASE_EXPIRE_TIME = 2L;

    //热数据缓存过期时间
     private final Long CACHE_EXPIRE_TIME = 8L;

     //缓冲区阈值
    private final Integer BUFF_COUNT = 1;

    @Autowired
    private RedisCache redisCache;

    @Autowired
    private UserCounterMapper userCounterMapper;

    @Autowired
    private FoodCounterMapper foodCounterMapper;

    /**
     * 创建缓冲区，这里直接使用 Caffeine 作为缓冲区
     */
    Cache<String,Object> cache = Caffeine.newBuilder()
            //设置缓存的初始容量为10
            .initialCapacity(10)
            //设置缓存最大容量为10000,超过100000之后就会按照LRU最近最少使用算法来移除缓存
            .maximumSize(100000)
            //设置要统计的缓存命中率
            .recordStats()
            //设置缓存移除通知，这里统一刷写数据库
            .removalListener((String key,Object value, RemovalCause cause) -> {
                  //打印日志
                System.out.println("计数服务-缓存条目被移除:key="+key+",value" + value + ",移除原因="+cause);
                //你可以根据移除原因执行不同的逻辑
                if(cause == RemovalCause.EXPIRED){
                    //如果是因为过期被移除，执行一些特定逻辑
                    System.out.println("计数服务-缓存条目因为过期被移除");
                }else if(cause == RemovalCause.SIZE){
                    //如果是因为缓存大小限制被移出,执行另一种逻辑
                    System.out.println("计数服务-缓存条目因为大小限制被移出");
                }else if(cause == RemovalCause.REPLACED){
                    //如果是因为替换被移除，执行另一种逻辑
                    System.out.println("技术服务-缓存条目因为替换被移出");

                }

            })
            //build 方法可以指定CacheLoader,在缓存不存在时通过CacheLoader的实现自动加载缓存
            .build();



    /**
     * 写入单个计数
     * @param objId  用户id或者美食id
     * @param objType 类型：用户或者美食
     * @param field counter_key
     * @param value counter_value
     * @return
     */
    @Override
    public ApiResult setCounter(Integer objId, String objType, String field, Integer value) {

        //这里我们使用hash结构来存储和判断
        String redisKey =  objType.equals("food") ?
                RedisKeyConstant.FOOD_COUNTER_KEY_PREFIX + objId //美食计数 key
                : RedisKeyConstant.USER_COUNTER_KEY_PREFIX + objId;
        // 判断美食计数服务是否存在
        Boolean bool = redisCache.hasKey(redisKey);
        //先计数自增+ 1/自增 -1
        if(value.equals(-1)){
            //如果是自减 -1
            Object countValue = redisCache.hGet(redisKey,field);
            //如果已经减为9 之后就不能再减了
            if(0<Integer.parseInt(countValue.toString())){

                redisCache.hIncr(redisKey,field,value);

            }


        }else{

            redisCache.hIncr(redisKey,field,value);

        }
        if(bool){
            //如果存在，这里需要缓存区来批量更新到数据库
            log.info("技术服务-非首次写缓存区。objId:{}.objType:{},field:{},value:{}",objId,objType,field,value);

            putBuffer(objId,objType,field,value);

        }else{

             //说明是第一次，写入数据库
             log.info("计数服务-首次先写数据库。objId:{},objType:{},field:{},value:{}",objId,objType,field,value);
             insertDbCounter(objId,objType,field,value);
        }

        return ApiResult.doSuccess();
    }
    /**
     * 插入数据库
     * @param objId
     * @param objType
     * @param field
     * @param value
     */
    private void insertDbCounter(Integer objId,String objType,String field,Integer value){

        if(objType.equals("food")){

            foodCounterMapper.addFoodCounter(objId,field,value,0,new Date());

        }else {

            userCounterMapper.addUserCounter(objId, field, value, 0, new Date());

        }


    }


    /*
     * Description: 写入缓冲区，达到缓冲区阈值大小后会刷写到数据库
     * @param null
     * @Return: {@link null}
     * @Author: Administrator
     * @Date: 2025/5/1 22:54
     */
    private synchronized  void putBuffer(Integer objId,String objType,String field,Integer value){
        //比如 like_count:food:1 like_count:user:1
        String cacheKey = field + ":" + objType + ":" + objId;
        Integer countValue = (Integer) cache.getIfPresent(cacheKey);
        //判断是否存在
        if(countValue !=null){
            //当前计数 + (+1/-1)
            log.info("计数服务-当前技术存在,写入缓冲区。cacheKey:{},countValue:{},value:{},buff_count:{}",cacheKey,countValue,value,BUFF_COUNT);
            cache.put(cacheKey,countValue + value);
            // 这里取消操作会为负值，所以这里计算阈值会用绝对表示
            if(Math.abs(countValue) + Math.abs(value) >= BUFF_COUNT){

                log.info("计数服务-缓冲区已满，开始更新数据库。cacheKey:{},countValue:{},value:{}",cacheKey,countValue,value);

                countValue = (Integer) cache.getIfPresent(cacheKey);
                //进行移除并写数据库、
                cache.invalidate(cacheKey);
                //更新数据库
                updateDBCounter(objId,objType,field,value,countValue);
            }
        }else {
             log.info("计数服务-当前计数不存在,写入缓冲区。cacheKey:{},countValue:{}",cacheKey,value);
             cache.put(cacheKey,value);
        }

    }

    /**
     * 缓冲区满之后会更新数据库
     * @param objId
     * @param objType
     * @param field
     * @param value
     */
    private void updateDBCounter(Integer objId,String objType,String field,Integer value,Integer cacheValue){



    }


    @Override
    public ApiResult getCounters(Integer objId, String objType, List<String> field) {
        return null;
    }
}
